package cn.coin.ox.common.erc20.service.impl;

import cn.coin.ox.common.erc20.config.ETHConfig;
import cn.coin.ox.common.erc20.contract.USDTKeyContract;
import cn.coin.ox.common.erc20.enums.ERC20ContractAddressEnums;
import cn.coin.ox.common.erc20.enums.EndpointsEnums;
import cn.coin.ox.common.erc20.model.UserWallet;
import cn.coin.ox.common.erc20.service.ERC20Service;
import cn.coin.ox.common.erc20.util.ETHUtil;
import cn.coin.ox.common.erc20.util.ERC20Util;
import io.github.novacrypto.bip39.MnemonicGenerator;
import io.github.novacrypto.bip39.SeedCalculator;
import io.github.novacrypto.bip39.Words;
import io.github.novacrypto.bip39.wordlists.English;
import io.github.novacrypto.hashing.Sha256;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.web3j.abi.FunctionEncoder;
import org.web3j.abi.FunctionReturnDecoder;
import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Type;
import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.crypto.*;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.DefaultBlockParameterName;
import org.web3j.protocol.core.methods.request.Transaction;
import org.web3j.protocol.core.methods.response.EthCall;
import org.web3j.protocol.core.methods.response.EthGetBalance;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.web3j.protocol.http.HttpService;
import org.web3j.tx.Transfer;
import org.web3j.utils.Convert;
import org.web3j.utils.Numeric;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;

@Service
public class ERC20ServiceImpl implements ERC20Service {

    /*** 日志 */
    private final static Logger log = LoggerFactory.getLogger(ERC20ServiceImpl.class);

    @Override
    public String generateMnemonics() {
        StringBuilder sb = new StringBuilder();
        byte[] entropy = new byte[Words.TWELVE.byteLength()];
        new SecureRandom().nextBytes(entropy);
        new MnemonicGenerator(English.INSTANCE)
                .createMnemonic(entropy, sb::append);
        String mnemonics = sb.toString();
        log.info("[ERC20-账户]---生成助记词结束,生成结果:{}", mnemonics);
        return mnemonics;
    }

    @Override
    public String createWallet(String password, String walletFileBasePath, String mnemonics) {
        String walletFilePath = "";
        try {
            if (StringUtils.isEmpty(mnemonics)) {
                // 生成助记词
                mnemonics = generateMnemonics();
            }
            // 根据助记词和密码生成种子seed
            byte[] seed = new SeedCalculator().calculateSeed(mnemonics, "");
            // 根据seed生成公钥、私钥等
            ECKeyPair ecKeyPair = ECKeyPair.create(Sha256.sha256(seed));
            Numeric.toHexStringWithPrefix(ecKeyPair.getPrivateKey());
            Numeric.toHexStringWithPrefix(ecKeyPair.getPublicKey());
            // 钱包文件保存路径，请替换位自己的某文件夹路径
            File walletBaseFile = new File(walletFileBasePath);
            //如果文件夹不存在则创建
            if (!walletBaseFile.exists() && !walletBaseFile.isDirectory()) {
                walletBaseFile.mkdir();
            }
            // 根据公钥 私钥 密码 得到 生成钱包文件keystore
            walletFilePath = WalletUtils.generateWalletFile(password, ecKeyPair, walletBaseFile, false);
        } catch (Exception e) {
            log.error("[ERC20-账户]---创建钱包失败，错误信息:{}", e.getMessage());
            return null;
        }
        return walletFilePath;
    }

    @Override
    public Credentials getCredentials(String walleFilePath, String passWord) {
        Credentials credentials = null;
        try {
            credentials = WalletUtils.loadCredentials(passWord, walleFilePath);
        } catch (Exception e) {
            log.error("[ERC20-账户]---获取web3j证书对象失败，错误信息:{}", e.getMessage());
        }
        return credentials;
    }

    @Override
    public UserWallet loadWallet(Credentials credentials) {
        String address = credentials.getAddress();
        BigInteger publicKey = credentials.getEcKeyPair().getPublicKey();
        BigInteger privateKey = credentials.getEcKeyPair().getPrivateKey();
        String privateKeyStr = Numeric.toHexStringWithPrefixZeroPadded(privateKey, Keys.PRIVATE_KEY_LENGTH_IN_HEX);
        log.error("\n[ERC20-账户]--加载钱包信息:\n[\n钱包地址:[{}] \n私钥:[{}] \n公钥:[{}]\n]",
                address, privateKeyStr, publicKey);
        UserWallet userWallet = new UserWallet();
        userWallet.setAddress(address);
        userWallet.setPrivateKey(privateKeyStr);
        userWallet.setPublicKe(publicKey);
        return userWallet;
    }

    @Override
    public Web3j createEthClient(EndpointsEnums endpoints) throws Exception {
        if(endpoints == null){
            // 设置为主网
            endpoints = EndpointsEnums.MAINNET;
        }
        //连接方式1：使用infura 提供的客户端
        Web3j web3j = Web3j.build(new HttpService("https://"+endpoints+".infura.io/v3/" + ETHConfig.API_INFURA_TOKEN));
        //连接方式2：使用本地客户端
//        Web3j web3j = Web3j.build(new HttpService("127.0.0.1:7545"));
        //测试是否连接成功
        String web3ClientVersion = web3j.web3ClientVersion().sendAsync().get().getWeb3ClientVersion();
        log.info("[ERC20-客户端] 创建Web3j成功, 版本号:{}", web3ClientVersion);
        return web3j;
    }

    @Override
    public BigDecimal getBlanceOf(Web3j web3j, String address) throws IOException {
        if (web3j == null){
            return null;
        }
        log.info("[ERC20-账户]---正在查询余额,地址为:{}", address);
        //第二个参数：区块的参数，建议选最新区块
        EthGetBalance balance = web3j.ethGetBalance(address, DefaultBlockParameter.valueOf("latest")).send();
        //格式转化 wei-ether
        String concat = Convert.fromWei(balance.getBalance().toString(), Convert.Unit.ETHER).toPlainString();
        BigDecimal blanceETH = new BigDecimal(concat);
        log.info("[ERC20-账户]---查询余额,余额为:[{}],地址为:{}", blanceETH, address);
        return blanceETH;
    }

    @Override
    public BigDecimal getErc20BalanceOf(Web3j web3j, String address, ERC20ContractAddressEnums contract) throws Exception {
        if (web3j == null){
            return null;
        }
        log.info("[ERC20-账户]---正在查询代币余额,地址为:{},代币:{}", address, contract.getName());
        String methodName = "balanceOf";
        List<Type> inputParameters = new ArrayList<>();
        List<TypeReference<?>> outputParameters = new ArrayList<>();
        Address fromAddress = new Address(address);
        inputParameters.add(fromAddress);
        TypeReference<Uint256> typeReference = new TypeReference<Uint256>() {};
        outputParameters.add(typeReference);
        Function function = new Function(methodName, inputParameters, outputParameters);
        String data = FunctionEncoder.encode(function);
        Transaction transaction = Transaction.createEthCallTransaction(address, contract.getContractAddress(), data);

        EthCall ethCall;
        BigDecimal balanceValue = BigDecimal.ZERO;
        try {
            ethCall = web3j.ethCall(transaction, DefaultBlockParameterName.LATEST).send();
            List<Type> results = FunctionReturnDecoder.decode(ethCall.getValue(), function.getOutputParameters());
            if(results == null || results.size()<=0){
                throw new Exception("查询代币余额失败!");
            }
            String value = String.valueOf(results.get(0).getValue());
            // 保留6位精度
            balanceValue = new BigDecimal(value).divide(new BigDecimal(contract.getWei()), 6, RoundingMode.HALF_DOWN);
        } catch (Exception e) {
            log.error("[ERC20-账户]---正在查询代币余额，地址为:{},代币:{}, 错误信息:{}", address, contract.getName(), e.getMessage());
            return null;
        }
        return balanceValue;
    }

    @Override
    public TransactionReceipt ethTransTo(EndpointsEnums endpoints, Credentials credentials, String addressTo, BigDecimal amount) throws Exception {
        // 创建Web3j客户端
        Web3j ethClient = createEthClient(endpoints);
        if (credentials == null){
            throw new Exception("交易失败, Credentials Web3j证书对象不能为空!");
        }
        // eth 精度为18位 --->
        BigDecimal bigDecimal = ETHUtil.convertETH(amount);
        TransactionReceipt send = Transfer.sendFunds(ethClient, credentials, addressTo, bigDecimal, Convert.Unit.WEI).sendAsync().get();
        log.info("Transaction complete:");
        log.info(" trans hash=" + send.getTransactionHash());
        log.info(" from :" + send.getFrom());
        log.info(" to:" + send.getTo());
        log.info(" gas used=" + send.getGasUsed());
        log.info(" status: " + send.getStatus());
        return send;
    }

    @Override
    public TransactionReceipt erc20TransTo(EndpointsEnums endpoints, Credentials credentials, String addressTo,
                                           BigDecimal amount, ERC20ContractAddressEnums contract) throws Exception {
        Web3j ethClient = createEthClient(endpoints);
        BigInteger gasPrice = ERC20Util.getGasPrice(ethClient);
//        BigInteger gasPrice =  Contract.GAS_PRICE;
//        BigInteger gasLimit = BigInteger.valueOf(Constants.GAS_LIMIT);
        // Gas Limit就是一次交易中gas的可用上限
        //以太坊的交易手续费为：TxFee = gas * gas Price，单位Gwei
//        gasPrice = BigInteger.valueOf(27000000000L);
        BigInteger gasLimit = BigInteger.valueOf(250000);
        TransactionReceipt receipt = null;
        // 加载合约
        if(ERC20ContractAddressEnums.USDT.getCode() == contract.getCode()){
            // USDT 合约交易
            USDTKeyContract keySCode = USDTKeyContract.load(contract.getContractAddress(), ethClient, credentials, gasPrice, gasLimit);
            System.out.println("================== 开始ERC20合约交易 ==================");
            BigInteger currentGasPrice = keySCode.requestCurrentGasPrice();
            // 交易金额： 10000 = 0.01
            BigInteger amountInt = amount.toBigInteger().multiply(contract.getWei());
            // 调用转账方法
            receipt = keySCode.transfer(addressTo, amountInt).sendAsync().get();
        }else {
            throw new Exception("该币种未对接!");
        }
        // 打印交易Hash
        System.out.println("[ERC20合约] 交易完成! 交易Hash: ---> " + receipt.getTransactionHash());
        return receipt;
    }


}
